﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Taxonomy;
using Microsoft.SharePoint.Taxonomy.ContentTypeSync;
using Microsoft.SharePoint.Utilities;

namespace TemplateLibraryConnector.Utility
{
    public class ContentTypeAssociationJob : SPAllSitesJobDefinition
    {


        public ContentTypeAssociationJob()
        {
        }

        public ContentTypeAssociationJob(string jobName, SPWebApplication webApp) : base(jobName, webApp)
        {
            Title = jobName;
        }

        public override void ProcessSite(Microsoft.SharePoint.SPSite site, SPJobState jobState)
        {
            AddPublishedContentTypesToAllLists(site);
        }

        public static void AddPublishedContentTypesToAllLists(SPSite site)
        {
            ULSLogger.LogMessage("Processing document libraries in site: " + site.Url);

            var publishedCtypes = GetPublishedContentTypes(site);

            if (publishedCtypes.Count > 0)
            {
                var webs = site.AllWebs;
                foreach (SPWeb web in webs)
                {
                    try
                    {
                        web.AllowUnsafeUpdates = true;
                        foreach (var pct in publishedCtypes)
                        {
                            var docLibList = GetLibrariesWithMissingContentType(pct, web);

                            AddContentTypeToList(pct, docLibList);
                        }
                    }
                    catch (Exception ex)
                    {
                        ULSLogger.LogError("An error occured during processing of content types for site" + web.Url, ex);
                    }
                    finally
                    {
                        web.AllowUnsafeUpdates = false;
                        web.Dispose();
                    }
                }
            }
        }

        public static void AddContentTypeToList(string pct, List<SPDocumentLibrary> docLibList)
        {
            try
            {
                for (var i = 0; i < docLibList.Count; i++)
                {
                    var keywordsEnabled = IsEnterpriseKeywordsAlreadyEnabled(docLibList[i]);
                    docLibList[i].ParentWeb.AllowUnsafeUpdates = true;
                    docLibList[i].ContentTypes.Add(docLibList[i].ParentWeb.AvailableContentTypes[pct]);
                    ULSLogger.LogMessage("Added content type " + pct + " in document library " + docLibList[i].Title + " for site " + docLibList[i].ParentWebUrl);
                    if (keywordsEnabled)
                        ReEnableEnterpriseKeywords(docLibList[i]);
                    docLibList[i].Update(); 
                    docLibList[i].ParentWeb.AllowUnsafeUpdates = false;
                }

                // foreach stuff whacks with "modified collection" error
                /* docLibList.ForEach((doclib) =>
                    {
                        //var keywordsEnabled =
                        //    IsEnterpriseKeywordsAlreadyEnabled(doclib);
                        var newDocButton = doclib.RootFolder.UniqueContentTypeOrder;
                        doclib.ContentTypes.Add(pct);
                        doclib.Update();
                        doclib.RootFolder.UniqueContentTypeOrder = newDocButton;
                        doclib.RootFolder.Update();
                        //if (keywordsEnabled)
                        //    ReEnableEnterpriseKeywords(doclib);

                    });*/
            }
            catch (Exception e)
            {
                // Something happened during adding of content types to the current site. Log and move on
                ULSLogger.LogError("An error occured during processing of content types. ", e);
            }
        }

        private static List<SPDocumentLibrary> GetLibrariesWithMissingContentType(string pct, SPWeb web)
        {
            //var allLibs = web.GetListsOfType(SPBaseType.DocumentLibrary);
                                
            /*var docLibList = web.GetListsOfType(SPBaseType.DocumentLibrary).Cast<SPList>()
                .Select(list => list as SPDocumentLibrary)
                .Where(library =>
                       library != null &&
                       !library.IsCatalog &&
                       !library.IsSiteAssetsLibrary &&
                       !library.Hidden &&
                       library.ContentTypesEnabled &&
                       library.ContentTypes[pct.Name] == null)
                .ToList();*/
            //-and $_.Title - notlike "*Site*" -and $_.Title -notlike "*Templates*" -and $_.Title -notlike "*Style*"


            var allLibs = web.GetListsOfType(SPBaseType.DocumentLibrary);
            var docLibList = new List<SPDocumentLibrary>() { };

            if (web.AvailableContentTypes[pct] == null)
                return docLibList;

            foreach (SPDocumentLibrary l in allLibs)
            {
                if (!l.IsCatalog && !l.IsSiteAssetsLibrary && !l.Hidden &&
                    l.ContentTypesEnabled && l.ContentTypes[pct] == null)
                    docLibList.Add(l);
            }

            if (docLibList.Count > 0)
                ULSLogger.LogMessage("Found : " + docLibList.Count + " document libraries with missing content type " + pct + " for web " + web.Url);
            
            return docLibList;
        }

        public static List<string> GetPublishedContentTypes(SPSite site)
        {
            var publishedCtypes = new List<string>();

            try
            {
                var taxSession = new TaxonomySession(site);
                var store = taxSession.DefaultKeywordsTermStore;

                if (store.ContentTypePublishingHub != null)
                {
                    using (var hub = new SPSite(store.ContentTypePublishingHub.AbsoluteUri))
                    {
                        var ctp = new ContentTypePublisher(hub);
                        publishedCtypes.AddRange(from SPContentType ct in hub.RootWeb.ContentTypes where ctp.IsPublished(ct) && ct.Id.IsChildOf(SPBuiltInContentTypeId.Document) select ct.Name);
                    }
                }

            }
            catch (Exception ex)
            {
                ULSLogger.LogError("An error occured when getting published content types for site: " + site.Url, ex);
            }

            return publishedCtypes;
            
        }

        private static void ReEnableEnterpriseKeywords(SPDocumentLibrary list)
        {
            try
            {
                var taxonomy = Assembly.LoadWithPartialName("Microsoft.SharePoint.Taxonomy");
                var settingsType = taxonomy.GetType("Microsoft.SharePoint.Taxonomy.MetadataListFieldSettings");

                object settings = settingsType.GetConstructor(new Type[] { typeof(SPList) }).Invoke(new object[] { list });
                settingsType.GetProperty("EnableKeywordsField", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(settings, true, null);
                settingsType.GetMethod("Update", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(settings, null);
                ULSLogger.LogMessage("Reebabled Enterprise Keywords on document library " + list.Title + "in site " + list.ParentWebUrl);

            }
            catch( Exception ex )
            {
                ULSLogger.LogError("An error occured when enabling enterprise keywords for list" + list.DefaultViewUrl + " on site " + list.ParentWeb.Url, ex);
                // No real harm done. Could not enable enterprise keywords again...
            }
        }

        private static bool IsEnterpriseKeywordsAlreadyEnabled(SPDocumentLibrary list)
        {
            try
            {
                var taxonomy = Assembly.LoadWithPartialName("Microsoft.SharePoint.Taxonomy");
                var settingsType = taxonomy.GetType("Microsoft.SharePoint.Taxonomy.MetadataListFieldSettings");

                object s = settingsType.GetConstructor(new Type[] {typeof (SPList)}).Invoke(new object[] {list});
                var a = settingsType.GetProperty("EnableKeywordsField", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(s, null);
                return Convert.ToBoolean(a);
            }
            catch( Exception ex)
            {
                ULSLogger.LogError("An error occured when checking if enterprise keywords is enabled for list " + list.DefaultViewUrl + " on site " + list.ParentWeb.Url, ex);
                //Whatever...
            }
            return false;
        }
    }
}
